﻿package peace.graphic.tween {
import flash.display.DisplayObject;
import flash.display.Shape;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.TimerEvent;
import flash.utils.Timer;


[Event(name="change", type="peace.graphic.tween.TweenEvent")]

[Event(name="end", type="peace.graphic.tween.TweenEvent")]

[Event(name="play", type="peace.graphic.tween.TweenEvent")]

/**
 * Проигрыватель для экземпляров Motion.
 * служит для отделения отрисовки от временных параметров анимации
 * для одного или нескольких клипов
 *
 * ВАЖНО: отсчёт кадров начинается с 0, а не с 1 (чтобы не мудрить с интервалами)
 *
 * @example
 <listing>
 package
 {
 import flash.display.Sprite;
 import flash.display.StageAlign;
 import flash.display.StageScaleMode;

 import peace.graphic.tween.Motion;
 import peace.graphic.tween.Tween;
 import peace.graphic.tween.TweenParameter;
 import peace.graphic.tween.easing.BounceEasing;

 public class Main extends Sprite
 {

 public function Main()
 {
 stage.align = StageAlign.TOP_LEFT;
 stage.scaleMode = StageScaleMode.NO_SCALE;
 stage.frameRate = 31;

 init();
 }

 private function init():void
 {
 var s:Sprite = new Sprite();
 s.graphics.beginFill(0xff0000);
 s.graphics.drawCircle(20,20,20);
 addChild(s);

 var t:Tween = new Tween(100);
 var m:Motion = new Motion(s, 100, new BounceEasing());
 m.addParameter(new TweenParameter("x", 100, 500));
 t.addMotion(m);
 t.play();
 }
 }
 }
 </listing>
 *
 *
 */
public class Tween extends EventDispatcher {
    /**
     * длительность в кадрах
     */
    public var durationFrames:uint;
    /**
     * зациклено ли
     */
    public var loop:Boolean = false;
    /**
     * играем ли в обратную сторону
     */
    public var reverse:Boolean = false;
    /**
     * коллекция Motion
     */
    public var motions:Array = new Array();
    /**
     * время, в кадрах
     */
    protected var time:int = 0;
    /**
     * играет ли сейчас
     */
    protected var isRunning:Boolean = false;

    protected var timer:Timer;
    /**
     * универсальный слушатель ENTER_FRAME
     */
    protected static const TWEEN_LISTENER:DisplayObject = new Shape();

    public function Tween(durationFrames:uint = 100, useTimer:Boolean = false, timerFps:uint = 25) {
        this.durationFrames = durationFrames;
        if (useTimer) {
            timer = new Timer(1000 / timerFps);
            timer.addEventListener(TimerEvent.TIMER, onEngine, false, 0, true);
        }
    }

    public function get running():Boolean {
        return isRunning;
    }

    public function get currentFrame():int {
        return time
    }

    public function get totalFrames():int {
        return durationFrames
    }

    /**
     * @param m Motion
     * @param renderMotionToBeginState Boolean
     * @return
     */
    public function addMotion(m:Motion, renderMotionToBeginState:Boolean = true):Motion {
        // lazy init
        if (motions.length == 0 && !timer) {
            TWEEN_LISTENER.addEventListener(Event.ENTER_FRAME, onEngine);
        }

        motions.push(m);

        if (renderMotionToBeginState) {
            m.render(reverse ? durationFrames : 0);
        }

        return m;
    }

    public function removeMotion(m:Motion):void {
        var index:int = motions.indexOf(m);
        if (index > -1) {
            motions.splice(index, 1);
        }

        if (motions.length == 0) {
            destroy();
        }
    }

    public function clearMotions():void {
        motions = [];
        destroy();
    }

    public function play(loop:Boolean = false, reverse:Boolean = false):void {
        this.loop = loop;
        this.reverse = reverse;

        if (timer) timer.start();
        isRunning = true;

        dispatchEvent(new TweenEvent(TweenEvent.PLAY, time));
    }

    public function stop():void {
        if (timer) timer.stop();
        isRunning = false;
    }

    public function gotoBegin():void {
        goto(reverse ? durationFrames : 0);
    }

    public function gotoEnd():void {
        goto(reverse ? 0 : durationFrames);
    }

    public function gotoAndStop(f:int):void {
        goto(f);
        stop();
    }

    public function nextFrame():void {
        goto(time == durationFrames ? 0 : time + 1)
    }

    public function prevFrame():void {
        goto(time == 0 ? durationFrames : time - 1)
    }

    public function gotoAndPlay(f:int):void {
        goto(f);
        play(loop, reverse);
    }

    /**
     * Чистим все ресурсы
     */
    public function destroy():void {
        stop();
        TWEEN_LISTENER.removeEventListener(Event.ENTER_FRAME, onEngine);
    }

    /**
     * Просто переход на кадр без изменения сосотояния проигрывания
     * @param f int
     */
    protected function goto(f:int):void {
        if (f < 0) {
            f = 0;
        }
        if (f > durationFrames) {
            f = durationFrames;
        }

        // не топчемся на месте
        if (f == time) return;

        time = f;
        var m:Motion;
        for each (m in motions) {
            m.render(time);
        }

        dispatchEvent(new TweenEvent(TweenEvent.CHANGE, f));

        if ((reverse && f == 0) || (!reverse && f == durationFrames)) {
            dispatchEvent(new TweenEvent(TweenEvent.END, f));
        }
    }

    protected function onEngine(e:Event):void {
        if (!isRunning) return;
        if (motions.length == 0) return;

        if (reverse) {
            prevFrame();
        } else {
            nextFrame();
        }

        // обновление экрана
        if (e is TimerEvent) {
            TimerEvent(e).updateAfterEvent();
        }

        // проверяем на конец анимации
        if (time == (reverse ? 0 : durationFrames) && !loop) {
            stop();
        }
    }
}
}
